﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace RingBufferStream.Utils.Database
{
  /// <summary>
  /// Helper method extensions that can be used by implementers of the <see cref="BaseStoredProcedure"/> class.
  /// </summary>
  public static class SqlServerExtensions
  {
    /// <summary>
    /// Converts a sequence of database big endian encoded bytes to a sequence little endian encoded bytes.
    /// </summary>
    /// <param name="bytes">Bytes enumeration in big endian encoding.</param>
    /// <returns>Bytes enumeration in little endian encoding.</returns>
    public static IEnumerable<int> FromDatabaseBytes(this IEnumerable<byte> bytes)
    {
      var reversed = bytes.Reverse().ToArray(); //SqlServer is big endian, windows is little endian
      return Enumerable
        .Range(0, reversed.Length/4) //retrieve every four bytes
        .Select(index => BitConverter.ToInt32(reversed, index*4))
        .Reverse(); //put bytes back in order
    }

    private static readonly int[] _databaseTransientErrorCodes = { 20, 64, 233, 1205, 10053, 10054, 10060, 40143, 40197, 40501, 40613 };
    public static bool IsTransient(this SqlError sqlError)
    {
      if (sqlError == null)
      {
        throw new ArgumentNullException("sqlError");
      }
      return _databaseTransientErrorCodes.Contains(sqlError.Number);
    }

    private static readonly int[] _databaseUnavailableErrorCodes = { 2, 5, 53, 64, 121, 952, 4060, 6005, 9002, 10054, 17142, 10060, 10061, 11001 };
    public static bool IsDatabaseUnavailable(this SqlError sqlError)
    {
      if (sqlError == null)
      {
        throw new ArgumentNullException("sqlError");
      }
      return _databaseUnavailableErrorCodes.Contains(sqlError.Number);
    }

    public static SqlParameter AddInputParameter<T>(this SqlCommand command, string name, T value)
    {
      if (command == null)
      {
        throw new ArgumentNullException("command");
      }
      if (command == null)
      {
        throw new ArgumentNullException("name");
      }
      return command.Parameters.AddWithValue(name, value);
    }

    [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"),
     SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
    public static SqlParameter AddOutputParameter<T>(this SqlCommand command, string name, SqlDbType? dbType = null)
    {
      if (command == null)
      {
        throw new ArgumentNullException("command");
      }
      if (name == null)
      {
        throw new ArgumentNullException("name");
      }
      if (dbType == null)
      {
        SqlParameter parameter = command.Parameters.AddWithValue(name, default(T));
        parameter.Direction = ParameterDirection.Output;
        return parameter;
      }
      else
      {
        var parameter = new SqlParameter(name, dbType.Value, -1) {Direction = ParameterDirection.Output};
        command.Parameters.Add(parameter);
        return parameter;
      }
    }

    [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
    public static SqlParameter AddInputOutputParameter<T>(this SqlCommand command, string name, T value, SqlDbType? dbType = null)
    {
      if (command == null)
      {
        throw new ArgumentNullException("command");
      }
      if (name == null)
      {
        throw new ArgumentNullException("name");
      }
      if (dbType == null)
      {
        SqlParameter parameter = command.Parameters.AddWithValue(name, value);
        parameter.Direction = ParameterDirection.InputOutput;
        return parameter;
      }
      else
      {
        var parameter = new SqlParameter(name, dbType.Value, -1)
          {
            Direction = ParameterDirection.InputOutput,
            Value = value
          };
        command.Parameters.Add(parameter);
        return parameter;
      }
    }

    [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
    public static dynamic DefaultValue(this IDataParameter parameter)
    {
      if (parameter == null)
      {
        throw new ArgumentNullException("parameter");
      }
      switch (parameter.DbType)
      {
        case DbType.AnsiString:
          return null;
        case DbType.Binary:
          return null;
        case DbType.Byte:
          return (byte)0;
        case DbType.Boolean:
          return false;
        case DbType.Currency:
          throw new NotSupportedException();
        case DbType.Date:
          return default(DateTime);
        case DbType.DateTime:
          return default(DateTime);
        case DbType.Decimal:
          return (decimal)0;
        case DbType.Double:
          return (double)0;
        case DbType.Guid:
          return default(Guid);
        case DbType.Int16:
          return (short)0;
        case DbType.Int32:
          return 0;
        case DbType.Int64:
          return (long)0;
        case DbType.Object:
          return null;
        case DbType.SByte:
          return (sbyte)0;
        case DbType.Single:
          return (float)0;
        case DbType.String:
          return null;
        case DbType.Time:
          return default(DateTime);
        case DbType.UInt16:
          return (ushort)0;
        case DbType.UInt32:
          return (uint)0;
        case DbType.UInt64:
          return (ulong)0;
        case DbType.VarNumeric:
          throw new NotSupportedException();
        case DbType.AnsiStringFixedLength:
          return null;
        case DbType.StringFixedLength:
          return null;
        case DbType.Xml:
          return null;
        case DbType.DateTime2:
          return default(DateTime);
        case DbType.DateTimeOffset:
          return default(DateTimeOffset);
        default:
          throw new ArgumentOutOfRangeException("parameter");
      }
    }
  }
}
